/**
 * HP Subscriber, Network, and Application Policy
 * <p/>
 * Copyright (C) 2009-2014 Hewlett-Packard Company
 */
package com.hp.snap.gr.task.impl;

import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.*;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.hp.snap.gr.action.ActionPlanService;
import com.hp.snap.gr.action.impl.ActionPlanServiceImpl;
import com.hp.snap.gr.exception.ActionException;
import com.hp.snap.gr.exception.BusinessException;
import com.hp.snap.gr.exception.GRMException;
import com.hp.snap.gr.model.GRStatus;
import com.hp.snap.gr.model.TaskGroup;
import com.hp.snap.gr.model.TaskTypes;
import com.hp.snap.gr.model.jaxb.ActionPlanRefType;
import com.hp.snap.gr.model.jaxb.ActionPlanType;
import com.hp.snap.gr.model.jaxb.TaskType;
import com.hp.snap.gr.task.GRMTaskService;
import com.hp.snap.gr.utils.*;

/**
 * <p>AbstractTaskServiceImpl</p>
 *
 * @author Joey Yi
 */
public abstract class AbstractGRSiteTaskServiceImpl implements GRMTaskService {

    public static final String TASK_NAME_NON = "_NON_";
    private static Logger logger = LoggerFactory.getLogger(AbstractGRSiteTaskServiceImpl.class);

    protected ActionPlanService actionPlanService = new ActionPlanServiceImpl();

    public static Map<String, String> config = new HashMap<String, String>();
    @Override
    public void setGlobalValue(String user, String password, String url1, String url2, String outputFileName,String taskId,String param) {
        config = new HashMap<String, String>();
        config.put(Constants.GET_SESSIONSERVER_CONFIG_USER, user);
        config.put(Constants.GET_SESSIONSERVER_CONFIG_PASSWORD, password);
        config.put(Constants.GET_SESSIONSERVER_CONFIG_URL1, url1);
        config.put(Constants.GET_SESSIONSERVER_CONFIG_URL2, url2);
        config.put(Constants.GET_SESSIONSERVER_CONFIG_FILENANME, outputFileName);
        config.put(Constants.GET_SESSIONSERVER_CONFIG_TASKID, taskId);
        config.put(Constants.GET_SESSIONSERVER_CONFIG_PARAM, param);
        actionPlanService.setGlobalValue(config);
    }

    @Override
    public boolean handle(TaskTypes taskType) throws GRMException, ActionException {

        // read local config
        GRMConfigManager.initGRLocalConfig(config);

        // read grm task config
        GRMTaskHelper.unmarshalGRM(getTaskConfigurationFileName());

        // authentication, in ordre to improve the performance, the step will be skiped.
        //        verifyUserPermission(taskType);

        // export gr global config
//        SSConfigUtils.exportGRGlobalConfig(taskType);

        // read global config
//        GRMConfigManager.initGRGlobalConfig();

        // get voltdb deployment configuration 
        GRMConfigManager.initLocalDeployment();
//        GRMConfigManager.initRemoteDeployment();

//        // get grm status
//        GRStatus status = GRStatusHelper.getGRStatus();
//        if (logger.isDebugEnabled()) {
//            logger.debug("Current GR status is: {}", status);
//        }
        // get task name config
        String taskName = getTaskName(taskType);

        // execute task
        if (TASK_NAME_NON.equals(taskName)) {
            logger.info("Command [{}] not applicable in GR.", taskType);
            throw new BusinessException("Command [" + taskType + "] not applicable in GR.");
        }

        return executeTask(taskName, taskType);
    }

    /**
     * Check the user name and password to make sure it's an permitted ConfigServer operation, and it also check
     * the file permissions which would be update or delete.
     */
    protected void verifyUserPermission(TaskTypes taskType) throws GRMException {
        if (!taskType.toString().startsWith(TaskGroup.SGM.toString())) {
            if (logger.isDebugEnabled()) {
                logger.debug("Skip permission verify.");
            }
            return;
        }

        if (logger.isDebugEnabled()) {
            logger.debug("Starting to verify user permission...");
        }

        if (GRMConfigManager.isSecure()) {
            String eIUMHome = GRMConfigManager.getEIUMHome();
            String username = GRMConfigManager.getUsername();
            String password = GRMConfigManager.getPassword();
            if (username == null || password == null) {
                throw new GRMException("The secure mode is enabled, then you should configure the username and password!");
            }

            // check connection to config server
            int exitStatus = SSConfigUtils.accessConfigServer(
                    eIUMHome + File.separator + "bin" + File.separator + "siucontrol -n ConfigServer -c getlog -login "
                            + username + " -pw " + password, true, password);

            if (exitStatus != 0) {
                if (exitStatus == SSConfigUtils.CONFIG_SERVER_FAILED && SSConfigUtils
                        .isIgnoreConfigServerFailure(taskType)) {
                    Constants.CONSOLE
                            .info("Cannot contact the configuration server, skip the permission verification.");
                } else {
                    throw new GRMException("Permission verify failed!");
                }
            } else {
                if (logger.isDebugEnabled()) {
                    logger.debug("User permission allowed.");
                }
            }
        } else {
            if (logger.isDebugEnabled()) {
                logger.debug("Skip verification for insecure mode.");
            }
        }
    }

    protected abstract boolean executeTask(String taskName, TaskTypes taskType) throws GRMException, ActionException;

    protected abstract String getTaskConfigurationFileName();

    protected String getTaskName(TaskTypes taskType) throws GRMException {
        String taskKey = taskType + "";
        String taskName = TASK_NAME_NON;
        if (GRMConfigManager.getGrLocalConfig().containsKey(taskKey)) {
            taskName = GRMConfigManager.getLocalConfigValue(taskKey);
        }

        if (StringUtils.isEmpty(taskName)) {
            throw new GRMException("No task configured for: TaskType={}");
        }

        if (logger.isDebugEnabled()) {
            logger.debug("Find task name [{}] for TaskType: {}", new Object[] { taskName, taskType});
        }

        return taskName;
    }

    protected boolean doTask(String taskName) throws GRMException, ActionException {
        TaskType task = GRMTaskHelper.getTaskMap().get(taskName);
        if (task == null) {
            throw new GRMException("The task name cannot be found from the grm task config file, name: " + taskName);
        }

        List<ActionPlanRefType> actionPlans = task.getActionPlan();

        if (actionPlans.isEmpty()) {
            return true;
        }
        if (logger.isDebugEnabled()) {
            logger.debug("the task:{} async attribute is:{}", new Object[] { taskName, task.isAsync() });
        }
        if (!task.isAsync()) {
            for (ActionPlanRefType actionPlan : actionPlans) {
                ActionPlanType actionPlanType = GRMTaskHelper.getActionPlanMap().get(actionPlan.getName());
                try {
                    actionPlanService.execute(actionPlanType, task.isAsync());
                } catch (ActionException e) {
                    logger.error("Task execute failed: " + taskName, e);
                    throw e;
                }
            }
        } else {
            int threadPoolSize = Integer.parseInt(GRMConfigManager
                    .getLocalConfigValue(Constants.CFG_GRL_ASYNC_ACTION_PLAN_THREAD_SIZE, "4"));
            ExecutorService executorService = Executors.newFixedThreadPool(threadPoolSize);
            CountDownLatch startGate = new CountDownLatch(1);
            CountDownLatch endGate = new CountDownLatch(actionPlans.size());
            if (logger.isDebugEnabled()) {
                logger.debug("the actionPlans size is:{}", actionPlans.size());
            }

            List<ActionPlanRunner> runners = new ArrayList<ActionPlanRunner>(actionPlans.size());

            for (ActionPlanRefType actionPlan : actionPlans) {
                ActionPlanType actionPlanType = GRMTaskHelper.getActionPlanMap().get(actionPlan.getName());
                runners.add(new ActionPlanRunner(startGate, endGate, actionPlanService, actionPlanType));
            }

            try {
                startGate.countDown();

                List<Future<Map<String, Object>>> futures = executorService.invokeAll(runners);

                // start to execute action plans

                // wait all the action plans are executed
                endGate.await();

                if (logger.isDebugEnabled()) {
                    logger.debug("All action plans are executed.");
                }

                for (Future<Map<String, Object>> future : futures) {

                    // result null means action plan execute failed
                    if (future.get() == null) {
                        logger.error("Task execute failed: " + taskName);
                        return false;
                    }
                }

            } catch (InterruptedException e) {
                logger.error(e.getMessage(), e);
                return false;
            } catch (Exception e) {
                logger.error(e.getMessage(), e);
                return false;
            }
        }
        if (!StringUtils.isEmpty(task.getNext())) {
            doTask(task.getNext());
        }
        return true;
    }

    protected boolean sendSNMPTrap() {
        try {
            SNMPTrapHelper.sendTrap("GR task executed failed.");
        } catch (ActionException e) {
            logger.error(e.getMessage(), e);
            return false;
        }
        return true;
    }

    public static class ActionPlanRunner implements Callable<Map<String, Object>> {
        private CountDownLatch startGate;
        private CountDownLatch endGate;

        private ActionPlanService actionPlanService;
        private ActionPlanType actionPlanType;

        public ActionPlanRunner(CountDownLatch startGate, CountDownLatch endGate,
                ActionPlanService actionPlanService, ActionPlanType actionPlanType) {
            this.startGate = startGate;
            this.endGate = endGate;
            this.actionPlanService = actionPlanService;
            this.actionPlanType = actionPlanType;
        }

        @Override
        public Map<String, Object> call() throws Exception {

            if (logger.isDebugEnabled()) {
                logger.debug("Running a actionPlanType:{}", actionPlanType.getName());
            }

            startGate.await();

            Map<String, Object> result;

            try {
                result = actionPlanService.execute(actionPlanType, true);
            } catch (ActionException e) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Action plan executed failed: name={}, outputs=null", actionPlanType.getName());
                }
                return null;
            } finally {
                endGate.countDown();
            }

            if (logger.isDebugEnabled()) {
                logger.debug("Action plan executed successful: name={}, outputs={}", actionPlanType.getName(), result);
            }

            return result;
        }
    }

}
